﻿#pragma once

#include <QDomDocument>
#include <QFile>
#include <QString>

/**
 * @brief 全局的系统配置信息读写模块，用xml格式保存配置信息。提供全局对象g_settingData，请勿生成其他对象。
 *        配置信息保存在系统工作目录的 SettingData.xml 文件中。
 *        注意：SettingData类没有设计成线程安全。不可在多个线程中同时访问。
 */
class SettingData
{
private:
    QDomDocument domDoc;
    QDomElement docElement;

    QString filePath;       //用于保存系统启动时的工作目录。工作目录有可能在程序运行过程中被更改，所以需要事先保存。

    /**
     * @brief SpliteIndex 将source中包含的"[数值]"区分出来，将数值放入index。index默认值会被设置为0.
     * @param source
     * @param index
     */
    void SpliteIndex(QString& source, int& index) const
    {
        int pos1 = source.indexOf("[");
        int pos2 = source.indexOf("]");
        if (pos1 >= 0 && pos2 > pos1)
        {
            index = source.mid(pos1 + 1, pos2 - pos1 - 1).toInt();
            source = source.left(pos1);
        }
        else
            index = 0;
    }
public:
    SettingData();
    SettingData(SettingData&) = delete;
    ~SettingData();

    /**
     * @brief 取Int类型的配置值。
     * @param pathName xml文件中的元素路径，比如：Devices/Device/ClassName。注意路径中不要包括xml文档的根节点。
     *                 如果需要访问元素的属性，比如Device的HasWarning属性，则路径写法是：Devices/Device::HasWarning。
     *                 如果某个路径内有多个名称相同的节点，可以使用“[顺序值]"语法来区分这些节点。比如，如果有3个Device节点，
     *                 则：Devices/Device[1]/ClassName会访问Devices下的第2个Device节点。
     * @param defaultValue 如果pathName指定的路径不存在，返回的默认值。
     * @return 配置值
     */
    inline int GetDataInt(const QString& pathName, const int defaultValue = 0);

    /**
     * @brief 取float类型的配置值。
     * @param pathName xml文件中的元素路径，比如：Devices/Device/ClassName。注意路径中不要包括xml文档的根节点。
     *                 如果需要访问元素的属性，比如Device的HasWarning属性，则路径写法是：Devices/Device::HasWarning。
     *                 如果某个路径内有多个名称相同的节点，可以使用“[顺序值]"语法来区分这些节点。比如，如果有3个Device节点，
     *                 则：Devices/Device[1]/ClassName会访问Devices下的第2个Device节点。
     * @param defaultValue 如果pathName指定的路径不存在，返回的默认值。
     * @return 配置值
     */
    inline float GetDataFloat(const QString& pathName, const float defaultValue = 0);

    /**
     * @brief 取string类型的配置值。
     * @param pathName xml文件中的元素路径，比如：Devices/Device/ClassName。注意路径中不要包括xml文档的根节点。
     *                 如果需要访问元素的属性，比如Device的HasWarning属性，则路径写法是：Devices/Device::HasWarning。
     *                 如果某个路径内有多个名称相同的节点，可以使用“[顺序值]"语法来区分这些节点。比如，如果有3个Device节点，
     *                 则：Devices/Device[1]/ClassName会访问Devices下的第2个Device节点。
     * @param defaultValue 如果pathName指定的路径不存在，返回的默认值。
     * @return 配置值
     */
    QString GetDataString(const QString& pathName, const QString& defaultValue = "");

    /**
     * @brief 设置Int类型的配置值。
     * @param pathName xml文件中的元素路径，比如：Devices/Device/ClassName。注意路径中不要包括xml文档的根节点。
     *                 如果需要访问元素的属性，比如Device的HasWarning属性，则路径写法是：Devices/Device::HasWarning。
     *                 如果某个路径内有多个名称相同的节点，可以使用“[顺序值]"语法来区分这些节点。比如，如果有3个Device节点，
     *                 则：Devices/Device[1]/ClassName会访问Devices下的第2个Device节点。
     * @param value 要设置的值
     * @note 本函数仅改变xml文档内容，更改后不会将文档保存回磁盘上。需要调用Save()函数保存xml文档。
     */
    inline void SetDataInt(const QString& pathName, const int value);

    /**
     * @brief 设置float类型的配置值。
     * @param pathName xml文件中的元素路径，比如：Devices/Device/ClassName。注意路径中不要包括xml文档的根节点。
     *                 如果需要访问元素的属性，比如Device的HasWarning属性，则路径写法是：Devices/Device::HasWarning。
     *                 如果某个路径内有多个名称相同的节点，可以使用“[顺序值]"语法来区分这些节点。比如，如果有3个Device节点，
     *                 则：Devices/Device[1]/ClassName会访问Devices下的第2个Device节点。
     * @param value 要设置的值
     * @note 本函数仅改变xml文档内容，更改后不会将文档保存回磁盘上。需要调用Save()函数保存xml文档。
     */
    inline void SetDataFloat(const QString& pathName, const float value);

    /**
     * @brief 设置string类型的配置值。
     * @param pathName xml文件中的元素路径，比如：Devices/Device/ClassName。注意路径中不要包括xml文档的根节点。
     *                 如果需要访问元素的属性，比如Device的HasWarning属性，则路径写法是：Devices/Device::HasWarning。
     *                 如果某个路径内有多个名称相同的节点，可以使用“[顺序值]"语法来区分这些节点。比如，如果有3个Device节点，
     *                 则：Devices/Device[1]/ClassName会访问Devices下的第2个Device节点。
     * @param value 要设置的值
     * @note 本函数仅改变xml文档内容，更改后不会将文档保存回磁盘上。需要调用Save()函数保存xml文档。
     */
    void SetDataString(const QString& pathName, const QString value);

    /**
     * @brief 读入并装载配置信息xml文件：系统工作目录/SettingData.xml
     */
    void Load();

    /**
     * @brief 将内存中的xml文档写入磁盘：系统工作目录/SettingData.xml
     * @note 在更改配置后，可调用本函数保存配置信息。建议不要频繁调用Save，而是在整体配置信息均修改完成后，一次性存入磁盘。
     */
    void Save();

    //Instance
    static SettingData instance;
};

inline int SettingData::GetDataInt(const QString& pathName, const int defaultValue /* = 0 */)
{
    QString str;
    str.sprintf("%d", defaultValue);
    str = GetDataString(pathName, str);
    return str.toInt();
}

inline float SettingData::GetDataFloat(const QString& pathName, const float defaultValue /* = 0 */)
{
    QString str;
    str.sprintf("%f", defaultValue);
    str = GetDataString(pathName, str);
    return str.toFloat();
}

inline void SettingData::SetDataInt(const QString& pathName, const int value)
{
    QString str;
    str.sprintf("%d", value);
    SetDataString(pathName, str);
}

inline void SettingData::SetDataFloat(const QString& pathName, const float value)
{
    QString str;
    str.sprintf("%f", value);
    SetDataString(pathName, str);
}

//以下宏定义用于简化使用
#define GET_SETTING_INT(pathName) SettingData::instance.GetDataInt(pathName)
#define GET_SETTING_FLOAT(pathName) SettingData::instance.GetDataFloat(pathName)
#define GET_SETTING_STRING(pathName) SettingData::instance.GetDataString(pathName)
#define SET_SETTING_INT(pathName,data) SettingData::instance.SetDataInt(pathName, data)
#define SET_SETTING_FLOAT(pathName,data) SettingData::instance.SetDataFloat(pathName, data)
#define SET_SETTING_STRING(pathName,data) SettingData::instance.SetDataString(pathName, data)
